package internal

import (
	"database/sql"
	"fmt"

	"gitee.com/hailib/mytree/errs"
	"github.com/jmoiron/sqlx"
	"go.uber.org/zap"
)

// Tx 事务
type Tx interface {

	// Commit 提交事务，如果提交事务失败将会自动回滚
	Commit() Error

	// Description 事务描述
	Description() string

	// Exec 执行一条 sql 语句，如果执行失败，将会自动回滚事务
	Exec(sql string, args ...interface{}) (sql.Result, Error)

	// Get 执行一条 sql 语句，如果执行失败，将会自动回滚事务
	Get(dest interface{}, cmd string, args ...interface{}) Error

	// Prepare 获取语句
	//   * 语句获取出错将会回滚事务并返回错误
	Prepare(query string) (*Stmt, Error)

	// Rollback 回滚事务，并返回传入的错误
	Rollback(err Error) Error

	// Rollbackf 回滚事务，并返回传入的错误
	Rollbackf(kind errs.Type, code int64, format string, args ...interface{}) Error

	// RowsAffected 获取受影响的行数
	//   * 返回受影响的行数
	//   * 如果获取行数出错则回滚事务
	RowsAffected(result sql.Result) (int64, Error)

	// Select 使用事务进行查询
	//   * 如果查询失败，事务将会被回滚
	Select(dest interface{}, query string, args ...interface{}) Error

	// CheckRowsAffected 确认受影响的行数，
	//   * 返回受影响的行数
	//   * 返回是否有错误
	//   * 如果行数不对将自动回滚事务
	CheckRowsAffected(result sql.Result, min int, max int) (int64, Error)
}

// transaction 事务封装
type transaction struct {
	description string   // 事务的描述
	logger      Logger   // 日志
	tx          *sqlx.Tx // 实际上的事务
}

// NewTx 封装事务
func NewTx(tx *sqlx.Tx, logger Logger, name string) Tx {
	mt := transaction{
		logger:      logger,
		description: name,
		tx:          tx,
	}
	return &mt
}

// NewTx 封装事务
func NewTxf(tx *sqlx.Tx, logger Logger, format string, args ...interface{}) Tx {
	msg := fmt.Sprintf(format, args...)
	return NewTx(tx, logger, msg)
}

// Commit 提交事务，如果事务提交失败将会被自动回滚
func (t *transaction) Commit() Error {
	t.logger.Info("tx commit", zap.String("tx", t.description))
	err := t.tx.Commit()
	if err != nil {
		return t.Rollbackf(errs.TransactionError, 210901194527, "tx commit error, tx: %s", t.description)
	}
	return nil
}

// Rollback 回滚事务，并返回传入的错误
func (t *transaction) Rollback(err Error) Error {
	t.logger.Info("tx rollback", zap.String("tx", t.description))
	for i := 0; i < 3; i++ {
		e := t.tx.Rollback()
		if e == nil {
			break
		}
		t.logger.Debug("rollback error", zap.String("name", t.description), zap.Bool("retry", i < 2))
	}
	return err
}

// Rollback 回滚事务，并返回由传入的参数生成的错误
func (t *transaction) Rollbackf(kind errs.Type, code int64, format string, args ...interface{}) Error {
	err := errs.New(kind, code, format, args...)
	return t.Rollback(err)
}

// Description 获取事务的描述
func (t *transaction) Description() string {
	return t.description
}

// Exec 执行 exec 语句，如果执行失败将会自动回滚事务
func (t *transaction) Exec(query string, args ...interface{}) (sql.Result, Error) {
	t.logger.Debug("tx exec:", zap.String("tx", t.description), zap.String("sql", query), zap.Any("args", args))
	result, err := t.tx.Exec(query, args...)
	if err != nil {
		return nil, t.Rollbackf(errs.DbUpdateError, 210901195200, "trans exec error, name: %s, error: %s, sql: %s", t.description, err.Error(), formatSql(query, args))
	}
	return result, nil
}

// Get 执行 Get 语句，如果执行失败将会自动回滚事务
func (t *transaction) Get(dest interface{}, cmd string, args ...interface{}) Error {
	t.logger.Debug("tx get", zap.String("tx", t.description), zap.String("sql", cmd), zap.Any("args", args))
	err := t.tx.Get(dest, cmd, args...)
	if err != nil {
		return t.Rollbackf(errs.DbQueryError, 210901195409, "trans get error, name: %s, error: %s, sql: %s", t.description, err.Error(), formatSql(cmd, args))
	}
	return nil
}

// RowsAffected 获取受影响的行数，
//   1. 返回受影响的行数
//   2. 返回是否有错误
//   3. 如果行数不对将自动回滚事务
func (t *transaction) RowsAffected(result sql.Result) (int64, Error) {
	count, err := result.RowsAffected()

	if err != nil {
		return 0, t.Rollbackf(errs.DbUpdateError, 210901163501, "get rows affected error, tx: %s, error: %s", t.description, err.Error())
	}

	return count, nil
}

// CheckRowsAffected 确认受影响的行数，
//
// 1. 返回受影响的行数
// 2. 返回是否有错误
// 3. 如果行数不对将自动回滚事务
func (t *transaction) CheckRowsAffected(result sql.Result, min int, max int) (int64, Error) {
	count, err := result.RowsAffected()

	if err != nil {
		return 0, t.Rollbackf(errs.DbUpdateError, 210901163501, "get rows affected error, tx: %s, error: %s", t.description, err.Error())
	}

	if count < int64(min) || count > int64(max) {
		return 0, t.Rollbackf(errs.DbUpdateError, 210901163354, "tx: %s, min: %d, max: %d, receive: %d", t.description, min, max, count)
	}

	return count, nil
}

// Prepare 格式化 sql
//
// 1. 如果格式化错误将会回滚事务
func (t *transaction) Prepare(query string) (*Stmt, Error) {
	xs, err := t.tx.Prepare(query)
	if err != nil {
		return nil, t.Rollback(errs.New(errs.InvalidParam, 210904170351, "prepare query error, query: %s, err: %s", query, err.Error()))
	}
	return NewStmt(xs, query, t, t.logger), nil
}

// Select 使用事务进行查询
//   * 如果查询失败，事务将会被回滚
func (t *transaction) Select(dest interface{}, query string, args ...interface{}) Error {
	t.logger.Debug("tx select", zap.String("tx", t.description), zap.String("sql", query), zap.Any("args", args))
	err := t.tx.Select(dest, query, args...)
	if err != nil {
		return t.Rollbackf(errs.DbQueryError, 210907110302, "select error, query: %s, error: %s, args: %v", query, err.Error(), args)
	}
	return nil
}
